﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.draw;

using TravellerLib;

namespace TravellerSectorBooklet {
  /// <summary>
  /// Class for the actual booklet creation
  /// </summary>
  class Booklet {
    /// <summary>
    /// Gets or sets the PDF file.
    /// </summary>
    /// <value>
    /// The PDF file.
    /// </value>
    public string PDFFile { get; set; }

    // storage for PDF manipulation objects
    private PdfContentByte canvas;
    private Document document;
    private PdfWriter writer;
    private Sector sector;
    /// <summary>
    /// Gets the sector.
    /// </summary>
    /// <value>
    /// The sector.
    /// </value>
    public Sector Sector { get { return sector; } }
    public Rectangle PageSize { get; set; }
    public float Margin { get; set; }

    // Fonts used within the PDF.
    private BaseFont SANSSERIF;
    private BaseFont SERIF;
    private BaseFont FIXEDWIDTH;
    private BaseFont MARCELLUS;
    private BaseFont MARCELLUSSC;
    private BaseFont DEFAULT;

    // storage for index generation
    private Dictionary<string, List<int>> index;

    private int chapterNumber = 1;
    private float sectorRadius;
    private float subSectorRadius;

    /// <summary>
    /// Initializes a new instance of the <see cref="Booklet" /> class.
    /// </summary>
    /// <param name="sectorFile">The sector file.</param>
    /// <param name="pdfFile">The PDF file.</param>
    /// <param name="metadataFile">The metadata file.</param>
    /// <param name="name">The name of the sector, if overriding.</param>
    /// <exception cref="System.ArgumentNullException">sector</exception>
    /// <exception cref="System.IO.FileNotFoundException">Font file is missing.;@fonts\marcellus.regular.ttf</exception>
    public Booklet(string sectorFile, string pdfFile, string metadataFile = null, string name = null) {
      if (sectorFile == null)
        throw new ArgumentNullException("sector");
      if (pdfFile == null)
        throw new ArgumentNullException("pdf");
      PDFFile = pdfFile;
      sector = Sector.LoadSector(sectorFile, metadataFile, name);

      // two Marcellus fonts are required and included

      if (File.Exists("fonts/marcellus.regular.ttf"))
        MARCELLUS = BaseFont.CreateFont("fonts/marcellus.regular.ttf", BaseFont.WINANSI, true);
      else
        throw new FileNotFoundException("Font file is missing.", @"fonts\marcellus.regular.ttf");
      if (File.Exists("fonts/marcellus-sc.regular.ttf"))
        MARCELLUSSC = BaseFont.CreateFont("fonts/marcellus-sc.regular.ttf", BaseFont.WINANSI, true);
      else
        throw new FileNotFoundException("Font file is missing.", @"fonts\marcellus-sc.regular.ttf");

      // Set up generic fonts. Assignments:
      //
      // SANSERIF: Arial if present, default Helvetica substitution if not
      // SERIF: Times New Roman if present, default Times Roman substitution if not
      // FIXEDWIDTH: Consolas if present, default Courier substitution if not

      if (File.Exists(System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "/arial.ttf"))
        SANSSERIF = BaseFont.CreateFont(System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "/arial.ttf", BaseFont.WINANSI, true);
      else
        SANSSERIF = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.WINANSI, false);

      if (File.Exists(System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "/times.ttf"))
        SERIF = BaseFont.CreateFont(System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "/times.ttf", BaseFont.WINANSI, true);
      else
        SERIF = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.WINANSI, false);

      if (File.Exists(System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "/consola.ttf"))
        FIXEDWIDTH = BaseFont.CreateFont(System.Environment.GetFolderPath(Environment.SpecialFolder.Fonts) + "/consola.ttf", BaseFont.WINANSI, true);
      else
        FIXEDWIDTH = BaseFont.CreateFont(BaseFont.COURIER, BaseFont.WINANSI, false);

      // replace the following the change the default text font.  E.g.
      //    DEFAULT = SERIF;
      // to use a serif font.
      DEFAULT = SANSSERIF;
      PageSize = iTextSharp.text.PageSize.HALFLETTER;
      Margin = 36;
    }

    /// <summary>
    /// Writes the booklet.
    /// </summary>
    /// <remarks>
    /// The various writing functions are <em>ugly</em>.  Not much can be done about that; iText isn't very pretty, and PDF is even worse.
    /// </remarks>
    public void WriteBooklet() {
      // Using half letter sized pages with half inch margin on each side.
      document = new Document(PageSize, Margin, Margin, Margin, Margin);
      subSectorRadius = hexSize(PageSize.Width - 2 * Margin, PageSize.Height - 2 * Margin);
      sectorRadius = hexSize(PageSize.Width - 2 * Margin, PageSize.Height - 2 * Margin, true);
      writer = PdfWriter.GetInstance(document, new StreamWriter(PDFFile).BaseStream);
      document.Open();
      canvas = writer.DirectContent;

      index = new Dictionary<string, List<int>>();

      writeTitlePage();
      writeCopyrightPage();
      writeTableOfContents();
      writeSectorMap();
      for (int i = 0; i < 16; i++)
        writeSubsectorPages(i);
      writeIndex();

      document.Close();
    }

    /// <summary>
    /// Writes the title page.
    /// </summary>
    private void writeTitlePage() {
      Paragraph paragraph;

      // fonts used on the page

      Font marcellus32i = new Font(MARCELLUS, 32, Font.ITALIC); // Marcellus, 32pt, italic
      Font marcellusSC52bi = new Font(MARCELLUSSC, 52, Font.BOLDITALIC); // MarcellusSC, 52pt, bold italic
      Font marcellus13b = new Font(MARCELLUS, 13, Font.BOLD); // Marcellus, 13pt, bold
      Font marcellus22i = new Font(MARCELLUS, 22, Font.ITALIC); // Marcellus, 22pt, italic
      Font marcellus24 = new Font(MARCELLUS, 24, Font.NORMAL); // Marcellus, 24pt


      // place some leading space

      paragraph = new Paragraph("", marcellus32i);
      paragraph.Add(Chunk.NEWLINE);
      paragraph.Add(Chunk.NEWLINE);
      document.Add(paragraph);

      // The sector name
      document.Add(new Paragraph(sector.Name, marcellus32i));

      // divider
      document.Add(new Chunk(new LineSeparator(5, 100, null, Element.ALIGN_CENTER, 36)));

      // TRAVELLER
      paragraph = new Paragraph();
      paragraph.Add(new Chunk("Traveller", marcellusSC52bi));
      paragraph.Add(new Chunk("®", marcellus13b));
      paragraph.Alignment = Element.ALIGN_RIGHT;
      document.Add(paragraph);

      // subtitle
      paragraph = new Paragraph("Science-Fiction Adventure in the Far Future", marcellus22i);
      paragraph.Alignment = Element.ALIGN_LEFT;
      paragraph.IndentationLeft = 63;
      document.Add(paragraph);

      // FFE credit
      document.Add(Chunk.NEWLINE);
      document.Add(Chunk.NEWLINE);
      document.Add(Chunk.NEWLINE);
      document.Add(new Paragraph("Far Future Enterprises", marcellus24));
      document.NewPage();
    }

    /// <summary>
    /// Writes the copyright page.
    /// </summary>
    private void writeCopyrightPage() {
      Paragraph paragraph;

      // fonts
      Font hyperlink = new Font(MARCELLUS, 10, Font.UNDERLINE, BaseColor.BLUE);
      Font traveller = new Font(MARCELLUSSC, 10, Font.BOLDITALIC);
      Font marcellus32i = new Font(MARCELLUS, 32, Font.ITALIC);
      Font marcellus10 = new Font(MARCELLUS, 10);
      Font marcellus10bi = new Font(MARCELLUS, 10, Font.BOLDITALIC);

      // leading space
      paragraph = new Paragraph("", marcellus32i);
      paragraph.Add(Chunk.NEWLINE);
      paragraph.Add(Chunk.NEWLINE);
      paragraph.Add(Chunk.NEWLINE);
      document.Add(paragraph);

      // special formatting for the string Traveller
      Chunk travellerChunk = new Chunk("Traveller", traveller);

      // copyright information
      paragraph = new Paragraph();
      paragraph.FirstLineIndent = 36;
      paragraph.Alignment = Element.ALIGN_JUSTIFIED;
      paragraph.Add(new Chunk("The ", marcellus10));
      paragraph.Add(travellerChunk);
      paragraph.Add(new Chunk(" game in all forms is owned by Far Future Enterprises. Copyright © 1977 - 2013 Far Future Enterprises. ", marcellus10));
      paragraph.Add(travellerChunk);
      paragraph.Add(new Chunk(" is a registered trademark of Far Future Enterprises. Far Future permits web sites and fanzines for this game, provided it contains this notice, that Far Future is notified, and subject to a withdrawal of permission on 90 days notice. The contents of this site are for personal, non-commercial use only. Any use of Far Future Enterprises's copyrighted material or trademarks anywhere on this web site and its files should not be viewed as a challenge to those copyrights or trademarks. In addition, any program/articles/file on this site cannot be republished or distributed without the consent of the author who contributed it.", marcellus10));
      document.Add(paragraph);

      // if we have credits, include them
      if (sector.Credits != null) {
        paragraph = new Paragraph();
        paragraph.FirstLineIndent = 36;
        paragraph.Alignment = Element.ALIGN_JUSTIFIED;
        paragraph.Font = marcellus10;
        paragraph.Add(Chunk.NEWLINE);
        paragraph.Add(new Chunk(sector.Credits, marcellus10));
        document.Add(paragraph);
      }

      // if we have an author, include them
      if (sector.Author != null) {
        paragraph = new Paragraph();
        paragraph.FirstLineIndent = 36;
        paragraph.Alignment = Element.ALIGN_JUSTIFIED;
        paragraph.Font = marcellus10;
        paragraph.Add(Chunk.NEWLINE);
        paragraph.Add(new Chunk("Data created by ", marcellus10));
        paragraph.Add(new Chunk(sector.Author, marcellus10));
        document.Add(paragraph);
      }

      // if we have a source, include it
      if (sector.Source != null) {
        paragraph = new Paragraph();
        paragraph.FirstLineIndent = 36;
        paragraph.Alignment = Element.ALIGN_JUSTIFIED;
        paragraph.Font = marcellus10;
        paragraph.Add(Chunk.NEWLINE);
        paragraph.Add(new Chunk("Adapted from ", marcellus10));
        paragraph.Add(new Chunk(sector.Source, marcellus10bi));
        document.Add(paragraph);
      }

      // if we have a publisher, include them
      if (sector.Publisher != null) {
        paragraph = new Paragraph();
        paragraph.FirstLineIndent = 36;
        paragraph.Alignment = Element.ALIGN_JUSTIFIED;
        paragraph.Font = marcellus10;
        paragraph.Add(Chunk.NEWLINE);
        paragraph.Add(new Chunk("Published by ", marcellus10));
        paragraph.Add(new Chunk(sector.Publisher, marcellus10));
        document.Add(paragraph);
      }

      // if we have a URL reference, link it
      if (sector.Reference != null) {
        paragraph = new Paragraph();
        Chunk c = new Chunk(sector.Reference, hyperlink);
        c.SetAnchor(sector.Reference);
        paragraph.FirstLineIndent = 36;
        paragraph.Font = marcellus10;
        paragraph.Add(Chunk.NEWLINE);
        paragraph.Alignment = Element.ALIGN_JUSTIFIED;
        paragraph.Add(c);
        document.Add(paragraph);
      }
      document.NewPage();
    }

    /// <summary>
    /// Writes the table of contents.
    /// </summary>
    private void writeTableOfContents() {
      Paragraph paragraph;
      Font marcellus24 = new Font(MARCELLUS, 24, Font.NORMAL);
      Font marcellus10 = new Font(MARCELLUS, 10, Font.NORMAL);

      // chapter heading
      paragraph = new Paragraph("Table of Contents", marcellus24);
      Chapter chapter = new Chapter(paragraph, 1);
      chapter.NumberDepth = 0;
      document.Add(chapter);
      document.Add(Chunk.NEWLINE);

      // place the sector map first
      paragraph = new Paragraph();
      paragraph.Alignment = Element.ALIGN_JUSTIFIED;
      paragraph.Add(new Chunk("Map of " + sector.Name, marcellus10));
      paragraph.Add(new Chunk(new DottedLineSeparator()));
      paragraph.Add(new Chunk("1", marcellus10));
      document.Add(paragraph);
      // add it to the index
      index.Add(sector.Name + ", Full Map", new List<int>(new int[] { 1 }));

      for (int i = 0; i < 16; i++) {
        // line for the given subsector
        paragraph = new Paragraph();
        paragraph.Alignment = Element.ALIGN_JUSTIFIED;
        paragraph.Add(new Chunk(sector[i].Name + " Subsector", marcellus10));
        paragraph.Add(new Chunk(new DottedLineSeparator()));
        // thanks to the formatting, subsector page numbers are predictable.
        int pageNumber = 2 + 2 * i;
        Anchor c = new Anchor(pageNumber.ToString(), marcellus10);
        c.Reference = "#pg" + pageNumber.ToString();
        paragraph.Add(c);
        document.Add(paragraph);
        // add subsector to index
        index.Add(sector[i].Name + " Subsector", new List<int>(new int[] { pageNumber }));
      }
      document.NewPage();
    }

    /// <summary>
    /// Writes the sector map.
    /// </summary>
    private void writeSectorMap() {
      writer.PageEmpty = false;
      drawRoutes(document.PageSize.Width - 2 * Margin, document.PageSize.Height - 2 * Margin, sector);
      hexGrid(document.PageSize.Width - 2 * Margin, document.PageSize.Height - 2 * Margin, true);
      drawBorders(document.PageSize.Width - 2 * Margin, document.PageSize.Height - 2 * Margin, sector);
      foreach (StarSystem s in sector.Systems)
        drawSystem(s, true);
      document.NewPage();
    }

    /// <summary>
    /// Writes the subsector pages.
    /// </summary>
    /// <param name="subsectorNumber">The subsector number.</param>
    private void writeSubsectorPages(int subsectorNumber) {
      SubSector subsec = sector[subsectorNumber];
      int pageNumber = 2 + 2 * subsectorNumber;
      writeSubsectorSystemList(subsec, pageNumber);
      writeSubsectorMap(subsec, pageNumber + 1);
    }

    /// <summary>
    /// Writes the subsector system list.
    /// </summary>
    /// <param name="subsector">The subsector.</param>
    /// <param name="pageNumber">The page number.</param>
    private void writeSubsectorSystemList(SubSector subsector, int pageNumber) {
      Paragraph paragraph;
      Anchor anchor;
      string str = "";

      Font marcellus24 = new Font(MARCELLUS, 24, Font.NORMAL);
      Font default10i = new Font(MARCELLUS, 10, Font.ITALIC);
      Font default9 = new Font(MARCELLUS, 9, Font.NORMAL);

      // chapter info
      paragraph = new Paragraph();
      anchor = new Anchor("The " + subsector.Name + " Subsector", marcellus24);
      anchor.Name = "pg" + pageNumber.ToString();
      paragraph.Add(anchor);
      Chapter chapter = new Chapter(paragraph, chapterNumber++);
      chapter.NumberDepth = 0;
      document.Add(chapter);
      document.Add(new Chunk(new LineSeparator(5, 100, null, Element.ALIGN_CENTER, 24)));

      // systems list
      paragraph = new Paragraph();
      paragraph.Leading = -24; // otherwise a huge gap after the chapter header
      PdfPTable table = new PdfPTable(new float[] { 20, 10f, 17.5f, 10f, 17.5f, 10f, 8f, 8.5f });
      table.WidthPercentage = 100;
      PdfPCell cell;

      // header row
      cell = new PdfPCell(new Phrase("Name", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("Hex", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("UWP", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("Base", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("Remarks", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("Zone", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("PBG", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      cell = new PdfPCell(new Phrase("Alleg", default10i));
      cell.Border = Rectangle.NO_BORDER;
      table.AddCell(cell);

      // loop through each system and add to table.
      foreach (var system in subsector.Systems.OrderBy(x => x.Key.Item1).ThenBy(x => x.Key.Item2).Select(x => x.Value)) {
        cell = new PdfPCell(new Phrase(system.Name, default9));
        cell.Border = Rectangle.NO_BORDER;
        table.AddCell(cell);

        cell = new PdfPCell(new Phrase(system.Hex, default9));
        cell.Border = Rectangle.NO_BORDER;
        table.AddCell(cell);

        cell = new PdfPCell(new Phrase(system.UWP, default9));
        cell.Border = Rectangle.NO_BORDER;
        table.AddCell(cell);

        cell = new PdfPCell(new Phrase(system.Bases, default9));
        cell.Border = Rectangle.NO_BORDER;
        cell.HorizontalAlignment = Element.ALIGN_CENTER;
        table.AddCell(cell);

        cell = new PdfPCell(new Phrase(system.Remarks, default9));
        cell.Border = Rectangle.NO_BORDER;
        table.AddCell(cell);

        // convert the TAS zone rating to a displayable value
        switch (system.Zone) {
          case Zone.Normal: str = ""; break;
          case Zone.Amber: str = "A"; break;
          case Zone.Red: str = "R"; break;
        }
        cell = new PdfPCell(new Phrase(str, default9));
        cell.Border = Rectangle.NO_BORDER;
        cell.HorizontalAlignment = Element.ALIGN_CENTER;
        table.AddCell(cell);

        // convert the planet/belts/giants values to a three digit number
        str = system.Planets.ToString() + system.Belts.ToString() + system.GasGiants.ToString();
        cell = new PdfPCell(new Phrase(str, default9));
        cell.Border = Rectangle.NO_BORDER;
        table.AddCell(cell);

        cell = new PdfPCell(new Phrase(system.Allegiance, default9));
        cell.Border = Rectangle.NO_BORDER;
        table.AddCell(cell);

        // add the system to the index
        if (!index.ContainsKey(system.Name + " "))
          index.Add(system.Name + " ", new List<int>());
        index[system.Name + " "].Add(pageNumber);
      }
      paragraph.Add(table);
      document.Add(paragraph);
      document.NewPage();
    }

    /// <summary>
    /// Writes the subsector map.
    /// </summary>
    /// <param name="subsector">The subsector.</param>
    /// <param name="pageNumber">The page number.</param>
    private void writeSubsectorMap(SubSector subsector, int pageNumber) {
      writer.PageEmpty = false;
      drawRoutes(document.PageSize.Width - 2 * Margin, document.PageSize.Height - 2 * Margin, subsector);
      hexGrid(document.PageSize.Width - 2 * Margin, document.PageSize.Height - 2 * Margin);
      drawBorders(document.PageSize.Width - 2 * Margin, document.PageSize.Height - 2 * Margin, subsector);
      foreach (StarSystem s in sector.Systems)
        drawSystem(s);
      document.NewPage();
    }

    /// <summary>
    /// Writes the index.
    /// </summary>
    private void writeIndex() {
      Paragraph paragraph;
      Anchor anchor;
      Phrase p;
      bool firstPage = true;

      Font marcellus24 = new Font(MARCELLUS, 24, Font.NORMAL);
      Font default10 = new Font(DEFAULT, 10, Font.NORMAL);
      Font marcellus10 = new Font(MARCELLUS, 10, Font.NORMAL);

      paragraph = new Paragraph("Index", marcellus24);
      Chapter chapter = new Chapter(paragraph, chapterNumber++);
      chapter.NumberDepth = 0;
      document.Add(chapter);

      // will use three columns for index
      ColumnText ct = new ColumnText(canvas);
      int column = 0; // which column to update
      string pn; // storage for converting page numbers

      // loop through entries in case insensitive alphabetical order
      foreach (string entry in index.Keys.OrderBy(x => x.ToLower())) {
        p = new Phrase();
        p.Add(new Chunk(entry + "   ", marcellus10));
        if (index[entry].Count == 1) { // single reference, easy case
          pn = index[entry][0].ToString();
          anchor = new Anchor(pn, marcellus10);
          anchor.Reference = "#pg" + pn;
          p.Add(anchor);
        } else { // multiple references
          int refs = index[entry].Count; // avoid repeatedly dereferencing
          for (int i = 0; i < refs; i++) {
            if (i != 0) // not the first reference, add comma
              p.Add(new Chunk(", ", marcellus10));
            pn = index[entry][i].ToString();
            anchor = new Anchor(pn, marcellus10);
            anchor.Reference = "#pg" + pn;
            p.Add(anchor);
          }
        }
        ct.AddText(p);
        ct.AddText(Chunk.NEWLINE);
      }

      // all entries added, now need to render

      int status = 0;
      float columnWidth = (PageSize.Width - 2 * Margin - 12) / 3f;
      Rectangle[] columns = new Rectangle[] { 
        new Rectangle(Margin, Margin, Margin + columnWidth, PageSize.Height - Margin), 
        new Rectangle(Margin + columnWidth + 6, Margin, Margin + 2 * columnWidth + 6, PageSize.Height - Margin), 
        new Rectangle(Margin + 2 * columnWidth + 12, Margin, PageSize.Width - Margin, PageSize.Height - Margin) };
      while (ColumnText.HasMoreText(status)) {
        Rectangle r = new Rectangle(columns[column]);
        if (firstPage) // less space on first page
          r.Top -= 2 * Margin;
        ct.SetSimpleColumn(r);
        if (!firstPage)
          ct.YLine = PageSize.Height - Margin;
        else // we need extra vertical space on the first page of the index
          ct.YLine = PageSize.Height - 3 * Margin;
        status = ct.Go();
        column = (column + 1) % 3;
        if (column == 0) { // if column returns to 0, we need a new page
          document.NewPage();
          firstPage = false;
        }
      }
      document.NewPage();
    }
    private const float SQRT3 = 1.7320508075688772935274463415058723669428052538104f;
    private float hexSize(float width, float height, bool sector = false, float border = 1) {
      float w, h;
      w = 2f * (width - 2f * border) / (25f * (sector ? 4 : 1));
      h = 2f * (height - 2f * border) / (21 * SQRT3 * (sector ? 4 : 1));
      return w <= h ? w : h;
    }

    private void hexGrid(float width, float height, bool sector = false, float border = 1f) {
      float radius = sector ? sectorRadius : subSectorRadius;
      canvas.SaveState();
      canvas.SetLineWidth(0.1f);
      canvas.SetColorFill(BaseColor.BLACK);
      canvas.SetColorStroke(BaseColor.BLACK);

      float newWidth, newHeight;
      float cx, cy;
      float dx, dy;

      if (sector) {
        newWidth = 2 * border + 97 * radius / 2f;
        newHeight = 2 * border + 81 * SQRT3 * radius / 2f;
      } else {
        newWidth = 2 * border + 25 * radius / 2f;
        newHeight = 2 * border + 21 * SQRT3 * radius / 2f;
      }
      dx = Margin + (width - newWidth) / 2f; dy = Margin + (height - newHeight) / 2;

      canvas.Rectangle(dx, dy, newWidth, newHeight);
      canvas.Stroke();
      if (sector) {
        cx = (newWidth - 2 * border) / 4;
        cy = (newHeight - 2 * border) / 4;
        for (int x = 1; x <= 3; x++) {
          canvas.MoveTo(dx + x * cx, dy);
          canvas.LineTo(dx + x * cx, dy + newHeight);
          canvas.Stroke();
        }
        for (int y = 1; y <= 3; y++) {
          canvas.MoveTo(dx, y * cy + dy);
          canvas.LineTo(dx + newWidth, y * cy + dy);
          canvas.Stroke();
        }
      }

      Rectangle r = new Rectangle(dx, dy, dx + newWidth, dy + newHeight);

      for (int x = -1; x <= (sector ? 32 : 8); x++)
        for (int y = -1; y <= (sector ? 40 : 10); y++) {
          cx = dx + border + radius + 3 * radius * x / 2f;
          cy = dy + border + SQRT3 * radius / 2f + y * SQRT3 * radius + (1 - (x & 1)) * SQRT3 * radius / 2f;
          canvas.ClippedRegularPolygon(r, cx, cy, radius, 6);
          canvas.Stroke();
        }

      canvas.RestoreState();
    }
    private void drawSystem(StarSystem system, bool asSector = false) {
    }
    private void drawRoutes(float width, float height, Sector sector, float border = 1) {
      Tuple<float, float> pos;
      float newWidth = 2 * border + 97 * sectorRadius / 2f;
      float newHeight = 2 * border + 81 * SQRT3 * sectorRadius / 2f;
      float dx = Margin + (width - newWidth) / 2f, dy = Margin + (height - newHeight) / 2;
      Rectangle rect = new Rectangle(dx, dy, dx + newWidth, dy + newHeight);
      foreach (Route r in sector.Routes) {
        canvas.SetColorStroke(new BaseColor(r.Color.Item1, r.Color.Item2, r.Color.Item3));
        pos = drawCoordinates(r.Start, true);
        canvas.MoveTo(pos.Item1, pos.Item2);
        pos = drawCoordinates(r.End, true);
        canvas.ClipLineTo(rect, pos.Item1, pos.Item2);
        canvas.SetLineWidth(1);
        canvas.Stroke();
      }
    }
    private void drawRoutes(float width, float height, SubSector subsector, float border = 1) {
      Tuple<float, float> pos;
      float newWidth = 2 * border + 25 * subSectorRadius / 2f;
      float newHeight = 2 * border + 21 * SQRT3 * subSectorRadius / 2f;
      float dx = Margin + (width - newWidth) / 2f, dy = Margin + (height - newHeight) / 2;
      Rectangle rect = new Rectangle(dx, dy, dx + newWidth, dy + newHeight);
      foreach (Route r in subsector.Parent.Routes.Where(x => inSubSector(subsector, x.Start) || inSubSector(subsector, x.End))) {
        canvas.SetColorStroke(new BaseColor(r.Color.Item1, r.Color.Item2, r.Color.Item3));
        pos = drawCoordinates(r.Start, subsector);
        canvas.MoveTo(pos.Item1, pos.Item2);
        pos = drawCoordinates(r.End, subsector);
        canvas.ClipLineTo(rect, pos.Item1, pos.Item2);
        canvas.SetLineWidth(5);
        canvas.Stroke();
      }
    }
    private void drawBorders(float width, float height, Sector sector) { }
    private void drawBorders(float width, float height, SubSector subsector) { }
    private Tuple<float, float> drawCoordinates(string t, SubSector s, float border = 1) {
      return drawCoordinates(t, false, border, (s.SectorPart - 'A') % 4, (s.SectorPart - 'A') / 4);
    }
    private Tuple<float, float> drawCoordinates(string t, bool sector = false, float border = 1, int offx = 0, int offy = 0) {
      int x, y;
      float radius = sector ? sectorRadius : subSectorRadius;
      Tuple<int, int> tc = TravellerUtils.TravellerToCartesian(t);
      x = tc.Item1 - offx * 8 - 1; y = tc.Item2 - offy * 10;
      y = (sector ? 42 : 10) - y;
      float newWidth, newHeight;
      float cx, cy;
      float dx, dy;
      float width = document.PageSize.Width - 2 * Margin, height = document.PageSize.Height - 2 * Margin;

      if (sector) {
        newWidth = 2 * border + 97 * radius / 2f;
        newHeight = 2 * border + 81 * SQRT3 * radius / 2f;
      } else {
        newWidth = 2 * border + 25 * radius / 2f;
        newHeight = 2 * border + 21 * SQRT3 * radius / 2f;
      }
      dx = Margin + (width - newWidth) / 2f; dy = Margin + (height - newHeight) / 2;
      cx = dx + border + radius + 3 * radius * x / 2f;
      cy = dy + border + SQRT3 * radius / 2f + y * SQRT3 * radius + (1 - (x & 1)) * SQRT3 * radius / 2f;
      return Tuple.Create(cx, cy);
    }

    private bool inSubSector(SubSector s, string tc) {
      Tuple<int, int> c = TravellerUtils.TravellerToCartesian(tc);
      int x = c.Item1 - 8 * ((s.SectorPart - 'A') % 4);
      int y = c.Item2 - 10 * ((s.SectorPart - 'A') / 4);
      if (x < 1 || x > 8 || y < 1 || y > 10)
        return false;
      return true;
    }
  }
}
